[译]数据分区策略及最佳实践

本文翻译自Azure architecture-center的data-partitioning,部分有删改

在许多大规模的解决方案里,数据被分为独立管理和访问的分区。在分区的策略的选择上需要十分谨慎,以达到分扬长避短的效果。分区可以提升可拓展性,隔离资源减小竞争和优化性能。分区的另外一个好处是可以提供根据使用模式来分离数据的机制。比如,你可以将较老的不常用的冷数据存储归档存储在便宜的设备上。

为什么要数据分区

大多数云应用或云服务提供数据存储和数据检索操作。应用使用的数据存储设计会给系统的性能,吞吐量和可拓展性产生显著的压力。一个大规模系统常用的技术是将数据分为若干分区。

在这篇文章,分区这个术语的意思是将数据物理上切分为多个数据存储单位,这和SQL Server的分表是不同的。

将数据分区可以带来一系列的好处。举例,它的作用有:

  • 提升可拓展性: 当你纵向拓展一个数据库系统,它最终会达到物理硬件的限制。如果你将数据切为不同的分区,每个分区分布在不同的服务器上,你可以无限地水平拓展系统。
  • 提高性能: 每个分片上的数据访问操作发生在规模更小的数据集上。如果数据被合理切分,分区可以令你的系统更有效率。比如,涉及多个数据分片的操作可以并行执行,每个分区可以被放在离使用它的应用更近的地方以减小网络延迟。
  • 提高可用性: 将数据分布在多个服务器上可以避免单点故障。如果一个服务器挂掉或者正在维护,只有它对应的数据分片不可用,对其他分片的操作仍可以正常进行。提高分区数量会减小一个服务器不可用导致的影响,因为不可用的数据比例更加小。对每个分区进行冗余备份可以进一步减小分区不可用的概率。分区同时也是将对可用性要求很高的关键数据和一般的低价值数据分离的办法。
  • 提高安全性: 取决于数据的特性和分区的方式,可以将敏感数据和非敏感数据存储在不同分区,同时也是不同的服务器或存储设备,所以可以为敏感数据所在分区实施特别的安全策略。
  • 提供操作的灵活性: 分区提供许多进行操作优化,最大化管理效率和最小化成本的机会。比如,你可以根据分区数据的重要性,为管理,监控,备份恢复和其他管理任务设置不同的策略。
  • 将数据存储与使用模式对应: 分区允许数据分区基于成本和数据库的内建特性部署在不同的数据存储单元上。比如,大二进制文件可以存储在BLOB存储单元,而结构化的数据则可以存储在文档数据库。

某些系统没有采用分区技术,因为它在设计上被认为是一项成本而不是优势,常见的原因如下:

  • 许多数据存储系统不支持跨分区的join,并且要在分区系统内维持引用完整性是很难的,因为这要求在应用代码里频繁地进行join和完整性检查。
  • 维护分区不总是简单的事情。在一个数据是易变的系统内,你可能需要定时重新平衡(rebalance)来减小资源竞争和热点。
  • 部分常见的工具天然不支持分区的数据。

设计分区

数据基于不同方式分区: 水平分区,纵向分区,或功能分区。选择哪种分区策略取决于你决定使用分区技术的原因以及使用数据的应用或服务的需求。

本文描述的分区放法不依赖于背后的数据存储技术,它们可以被用于许多数据存储,包括关系型和NoSql数据库。

分区策略

三种典型的分区策略:

  • 水平分区(sharding): 在这种策略下,每个分区自己都是完整的存储单元,全部的分区使用相同的数据结构(schema)。每个分区被称为一个shard,拥有数据的某个子集,比如电商领域的部分客户的所有订单。
  • 纵向分区: 在这种策略下,每个分区保存数据集的一部分字段。字段是根据它们的使用模式切分的。举个例子,经常访问的字段或许会被放在一个分区,而不常访问的字段放在另一个分区。
  • 功能分区: 在这种策略下,数据基于它在系统的使用情景聚集在一起。比如,一个包含发票业务和库存业务的电商系统可能会将发票数据存在一个分区,而库存数据存在另一个分区。

十分重要的一点是,以上三种策略是可以混合使用的。它们并不相互排斥,而且建议你在设计分区方式时将它们全部纳入考虑范围。比如,你可以将数据分为多个shard,然后在一个shard内进一步纵向切分数据。相似地,功能分区的数据同样可以水平切分为shards(也可以纵向切分)。

然而,策略的不同要求会产生一些冲突问题,你在设计一个在处理数据上满足性能要求的分区策略时必须评估和权衡这些问题。以下的章节会更详细地探讨每种分区策略。

水平分区(sharding)

图1展示了水平分区的概览。在这个例子中,产品库存被基于产品主键切分为多个shard。每个shard保存了一个连续范围的shard key对应的数据(A-G和H-Z),根据字母表顺序组织。

图1.水平分区

水平分区帮助你将负载分布到更多的机器上,减小了资源竞争,提升了性能。你可以通过另外服务器上增加更多的shard来将系统水平拓展。

实现这个分区策略最为关键的因素是分区键的选择,在系统开始运作之后再改变分区键是很困难的。分区键必须确保可以将数据分布到多个服务器,在多个shard间到达负载均衡。

注意不同的shard不需要拥有相似的数据量,更为重要的是平衡每个分区的请求数。某些shard可能非常大,但是保存数据项都是极少访问的。其余一些shard可能比较小,但是每个数据项都被频繁访问。另外保证每个shard在存储它的设备负载范围内也十分重要(容量和计算资源方面)。

如果你使用某个分区模式,一定要避免形成会影响性能和可用性的热点。比如,如果你对客户id而不是客户姓名首字母使用hash分桶,可以避免常见首字母和非常见首字母带来的不均匀数据分布。

选择shard key时要尽量避免未来需要将大shard拆分为小shard,或将小shard合并为大shard,或将修改分片存储的数据模式。这些操作会非常耗费时间,并且在操作的时候可能需要下线一个甚至多个分区。

如果shard有冗余,在一些副本在切分合并或修改时,其他副本仍然可以保持在线。但是在重新配置这些副本时,系统可能需要限制对相应数据的操作。譬如,这些副本的数据可以被标记为只读,以限制修改时造成的不一致性的影响范围。

纵向分区

最为常见的纵向分区作用是减少读取常访问数据的相关IO和性能成本。图2展示了纵向分区的一个例子,在这个例子中,数据项的不同属性被放在不同的分区。一个分区保存了常访问的数据,包括名称,描述和产品的价格信息。另一个分区保存了库存数目和最近下单日期。

图2.纵向分区

当展示产品详情给顾客时,应用规律性地查询产品名称,描述和价格。库存和最近下单日期保存在额外同一个分区,因为它们经常被一起查询。

这样的分区模式拥有额外的一个优势,即变化很少的数据(产品名,描述和价格)和相对动态的数据(库存和最近下单日期)分离了。应用可以通过缓存较少改变的数据来获得性能提升。

另一个常见的场景是提高敏感数据的安全性,比如你可以将信用卡号码和对应的信用卡安全校验码分开存储。

功能分区

对于可以识别每个业务领域或应用服务的边界的系统,功能分区可以提高隔离性和数据访问效率。另一个常见的用处是分离需要读写的数据和用于报告的只读数据。图3展示了库存数据与顾客数据分离的功能分区。

图3.功能分区

这样的分区策略可以帮助减少跨系统模块的数据访问链接。

为可拓展性设计分区

考虑每个分区的大小和负载并平衡它们使得数据分布到尽可能大的范围是十分重要的。然而,你也必须将数据切分到不会超出单点拓展能力的程度。

按照以下步骤来为针对可拓展性设计分区:

  1. 分析应用理解它的访问模式,比如每个查询返回的结果集大小,访问的频率,内在的延时和服务器端计算需要的资源。在许多情况下一小部分主要的实体占据了大多数的计算资源。
  2. 使用数据分析来决定当前和未来预计的规模增长,比如数据大小和工作负载,然后将数据分布到多个分区来满足规模增长。在水平分区策略下,选择合适的shard key是确保负载均匀的关键因素。
  3. 确保每个分区的可用资源足够应付数据集大小和吞吐量。比如,承载了某个分区的节点或许对它提供的存储空间,计算能力或网络资源有硬性的限制。如果数据存储和计算要求可能会超过这些限制,可能需要重新计划你的分区策略或将数据分布到更多节点上。比如,一个拓展的方式可能是将日志数据从核心应用特性中分离出来。你可以通过为两者使用不同的存储设备以防止总存储量超过节点限制来达到这个目标。
  4. 监控在用的系统以验证数据按预期分布且分区负载在承受范围内。分析监控数据后你可能发现使用情况和预期不同,在这种情况下或许需要重新平衡各个分区。如果不能这么做,则可能需要重新设计分区策略来获得平衡的负载。

注意一些云环境是按照基础架构的边界来分配资源的,请确保你要求的资源边界为未来在存储,计算力和带宽上的预期增长保留足够的空间。

为查询性能设计分区

查询性能通常可以从更小的数据集和并行执行查询中受益。每个分区应该包含整个数据集的一小部分,数据量的减小可以提升查询的性能。然而,分区并不能替代合理设计和配置数据库。比如,如果你在使用关系型数据库的话需要保证有需要的索引。

按照以下步骤来针对查询性能设计分区:

  1. 检验应用的需求和性能:
    • 用业务需求来决定一定需要快速完成的关键查询。
    • 监控系统并识别出执行缓慢的查询。
    • 证实最常用的查询是哪些。一个查询实例产生的成本可能微不足道,但累计的资源消耗不可忽视。或许将这些查询对应的数据分到一个独立的分区甚至存到缓存里会收到不错的效果。
  2. 将造成性能问题的数据进行分区:
    • 限制每个分区的大小,这样查询响应时间可以保证在目标范围内。
    • 如果你使用水平分区的话,合理设计shard key使得应用可以容易地找到目标分区,而不是遍历所有分区。
    • 考虑分区的位置。如果可能的话,将分区放在离应用和用户较近的地理位置。
  3. 如果实体有吞吐量和查询性能上的要求,基于该实体的功能进行分区。如果这仍然不能满足需求,再额外应用水平分区。在大多数案例里,应用单个分区策略即可,但有些情况下混合两种策略更加高效。
  4. 考虑使用并行的异步查询来提升性能。

为高可用设计分区

分区数据可以提高应用的可用性,因为数据集没有单点故障且每个子集可以被单独管理。对包含关键数据的分区进行冗余备份也可以提升可用性。

当设计和实现分区时,考虑以下会影响可用性的因素:

  1. 数据对于业务操作的重要程度。有些数据可能包含关键的数据比如发票明细或银行事务,其他数据可能包含相对不重要的数据,比如日志文件,性能追逐数据等等。将数据分好类之后,考虑:
    • 将关键数据存储到有合适备份计划的高可用分区。
    • 为不同重要程度的数据集建立不同的管理和监控机制或过程。将重要程度相同的数据放在同一个partition,以便一起以合适的频率定时备份。比如,存放银行事务的分区可能需要比存放日志数据的分区更频繁备份。
  2. 单独分区是如何管理的。设计支持独立管理维护的分区会带来几个好处。比如:
    • 如果一个分区不可用,它可以被独立地恢复而不影响应用访问其他分区的数据。
    • 将数据按地理位置分区允许为每个区域安排非高峰期的维护时间。确保分区不要太大义避免维护不按时完成。
  3. 是否将关键数据冗余备份到多个分区。这个策略可以提高可用性和性能,尽管它也引入了一一致性问题,因为一个分区的数据同步到其他副本需要一定时间,在这段时间内不同副本会有不同的数据。

理解分区如何影响设计和开发

使用分区会增加系统设计和开发的复杂度,即使系统初始时只有一个分区也应该将分区作为系统基础设计的一部分。如果你将分区放在后面再考虑,当系统开始遭遇性能和拓展性问题时,复杂度会大大提高,因为你有一个运行中的系统要维护。

在一些情况下,分区并不被视为重要的一部分,因为初始的数据集很小,单服务器可以轻松应对。这对于不希望拓展至大于初始数据集大小的系统来说或许是对的,但是许多商业系统需要随着用户增长而拓展,这种拓展往往伴随着数据集的增长。

意识到分区不总是大型数据存储的功能同样很重要。比如,一个小的数据存储设备或许会被数百个客户频繁访问,将数据进行分区可以帮助减小资源竞争并提高吞吐量。

在进行数据分区模式设计的时候考虑如下几点:

  • 尽可能将最常用的数据库操作访问的数据放在一起以避免跨分区的数据访问操作。跨分区查询比在分区内查询花费更多的时间,但是为一组查询优化多个分区也可能反过来影响其他查询。当你不能避免跨分区查询的时候,通过并行执行查询并在应用内聚合查询结果来最小化查询耗时。这种方法在一些情况下可能是不可行的,比如当需要将一个查询的结果用于下一个查询的情况。
  • 如果查询使用了相对静态的引用数据,比如邮政编码表或者产品列表,考虑将这些数据复制到所有的分区里,以减小在不同分区分别执行查询的需求。这种方式同时减小了引用数据集变成整个系统的流量热点。然而,同步引用数据发生的修改也带来了额外的成本。
  • 将分区模式对分区间数据一致性的影响纳入考虑范围,评估是否真的需要强一致性。相反,一个云应用常见的方式是实现最终一致性。每个分区的数据是分别更新的,然后应用的逻辑保证了所有的更新都是成功完成的,它也处理了最终一致性操作运行期间可能出现的不一致问题。
  • 考虑如何让查询定位到正确的分区。如果一个查询必须遍历所有分区来定位需要的数据,这会给性能带来严重的影响,即使是并行执行查询。使用纵向分区或功能分区的查询天然就有指定的分区。然而,水平分区(sharding)定位数据线则比较复杂,因为每个shard的模式都是相同的。一个典型的解决方案是维护一个用于查找定制数据项对应分区的映射。这个映射可以以应用程序切分数据的逻辑实现,或者用支持透明水平分区数据存储端实现。
  • 当使用水平分区策略时,考虑定期重新平衡各个shard。这可以帮助将数据按大小和工作负载均匀地分布在多个分区,以减小热点并提高查询效率,以满足物理存储设备的限制。但是这是一个复杂的任务,通常需要使用常用工具或进程。
  • 如果你达到了分区策略的物理限制,你可能需要将可拓展性延伸到另外一个层级。比如,如果分区已经是在数据库层级,然后遭遇了物理限制的问题,这可能意味着你需要将分区复制或移动到多个主机上。
  • 避免访问不同分区的事务。一些数据存储端实现了事务一致性和修改数据的完整性校验,但是仅对数据在单个分区内的情况有效。如果你需要跨分区的事务支持,你可能需要在应用程序里实现这部分逻辑,因为大多数分区系统并不提供这样的原生支持。

所有数据存储端都要求一些管理和监控活动。这些任务包括加载数据,备份和恢复数据,重新组织数据,和保证系统运行的正确性和高效性。

将以下会影响运维管理的因素纳入考虑范围:

  • 数据分区的情况下,如何实现合适的管理和运维任务。这些任务包括备份和恢复数据,归档数据,监控系统和其他管理任务。比如,在备份和恢复进行时如何维持逻辑上的一致性会是一个挑战。
  • 如何加载数据到不同的分区和将来自其他数据源的数据存入。为了防止分区过量地增长,你需要定期(或许是每月一次)归档和删除数据,这可能需要将数据转换为符合归档模式的格式。
  • 如何定位数据完整性问题。考虑运行一个定时进程来定位数据一致性问题,比如一个分区的数据引用了另外一个分区的不存在的数据。这个进程可能会尝试自动修复这些问题,或者给运维管理人员发警报提醒他们手动修复。比如,在一个电商应用,生成订单的进程需要新增数据记录到其他分区。如果这个进程挂掉,有可能出现有商品没有对应的订单号的问题。
本文是原文翻译,转载请注明:时间与精神的小屋 - [译]数据分区策略及最佳实践